package edu.del.newsreleasesystem.es.service;

import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import edu.del.newsreleasesystem.dto.NewsSearchResult;
import edu.del.newsreleasesystem.entity.News;
import edu.del.newsreleasesystem.es.document.NewsEsDocument;
import edu.del.newsreleasesystem.service.NewsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
import org.springframework.data.elasticsearch.core.query.highlight.Highlight;
import org.springframework.data.elasticsearch.core.query.highlight.HighlightField;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 负责将新闻数据（通过 newsId）从数据库查询后同步到 Elasticsearch 索引 的服务类
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class NewsEsIndexService {


    private final ElasticsearchOperations elasticsearchOperations;

    // ES 索引名称
    private static final String NEWS_INDEX = "news";

    private final NewsService newsService;

    // 异步执行，不阻塞主线程
    @Async
    public void initializeNewsIndexAsync() {
        // 执行初始化索引
        createNewsIndexIfNotExists();
    }

    /**
     * 创建索引（如果尚未存在）
     */
    public void createNewsIndexIfNotExists() {
        IndexOperations indexOps = elasticsearchOperations.indexOps(NewsEsDocument.class);
        if (!indexOps.exists()) {
            try {
                // 1. 创建索引
                indexOps.create();
                // 2. 创建并设置 Mapping
                indexOps.putMapping(indexOps.createMapping(NewsEsDocument.class));
                log.info("Elasticsearch 新闻索引创建成功: {}", NEWS_INDEX);

                // 3. 分页查询数据库，避免全量加载
                int page = 0;
                int size = 100;
                Page<News> newsPage;
                do {
                    newsPage = newsService.page(new Page<>(page, size));
                    List<News> newsList = newsPage.getRecords();

                    if (!newsList.isEmpty()) {
                        // 4. 转为 ES Document 列表
                        List<IndexQuery> indexQueries = newsList.stream()
                                .map(news -> new IndexQueryBuilder()
                                        .withObject(buildEsDocument(news))
                                        .build())
                                .collect(Collectors.toList());

                        // 5. 批量插入 ES
                        elasticsearchOperations.bulkIndex(indexQueries, IndexCoordinates.of(NEWS_INDEX));
                        log.info("已批量同步 {} 条新闻到 Elasticsearch，页码: {}", newsList.size(), page);
                    }

                    page++;
                } while (newsPage.hasNext());

            } catch (Exception e) {
                log.error("初始化 Elasticsearch 新闻索引失败", e);
            }
        } else {
            log.info("Elasticsearch 新闻索引已存在: {}", NEWS_INDEX);
        }
    }

    /**
     * 同步新闻到 ES（用于 PUBLISH / UPDATE）
     * 从数据库查询新闻，构建 ES Document，然后存储到索引
     *
     * @param newsId 新闻ID
     */
    public void syncNewsToEsOnPublishOrUpdate(Integer newsId) {
        try {
            // 1. 查询数据库
            News news = newsService.getNewsById(newsId);
            if (news == null) {
                log.warn("新闻ID={} 在数据库中不存在，无法同步到 ES", newsId);
                return;
            }

            // 2. 构建 ES Document
            NewsEsDocument esDoc = buildEsDocument(news);

            // 3. 构造 IndexQuery 并同步到 ES
            IndexQuery indexQuery = new IndexQueryBuilder()
                    .withObject(esDoc)
                    .build();

            elasticsearchOperations.index(indexQuery, IndexCoordinates.of(NEWS_INDEX));
            log.info("新闻已同步到 Elasticsearch 索引（发布/更新），新闻ID: {}", newsId);

        } catch (Exception e) {
            log.error("同步新闻到 ES 失败，新闻ID: {}", newsId, e);
        }

    }

    /**
     * 从 ES 中删除新闻（用于 DELETE 操作）
     *
     * @param newsId 新闻ID
     */
    public void syncNewsToEsOnDelete(Integer newsId) {
        try {
            // 1. 删除 ES 中的新闻
            elasticsearchOperations.delete(String.valueOf(newsId), NewsEsDocument.class);
            log.info("新闻已从 Elasticsearch 索引中删除，新闻ID: {}", newsId);
        } catch (Exception e) {
            log.error("从 Elasticsearch 索引中删除新闻失败，新闻ID: {}", newsId, e);
        }
    }

    /**
     * 搜索新闻：支持标题、作者、分类、内容的关键字搜索，返回高亮结果
     *
     * @param keyword 搜索关键词
     * @return 搜索结果列表，包含高亮字段
     */
    public List<NewsSearchResult> searchNews(String keyword) {

        // 加入排序
        SortOptions sortOptions = new SortOptions.Builder()
                .field(f -> f.field("publishTime").order(SortOrder.Desc))
                .build();

        // 1. 构建多字段匹配查询和高亮
        NativeQuery query = NativeQuery.builder()
                .withQuery(q -> q.multiMatch(
                        m -> m.fields("title", "author", "category", "content")
                                .query(keyword)
                ))
                .withHighlightQuery(new HighlightQuery(
                        new Highlight(
                                List.of(new HighlightField("title"),
                                        new HighlightField("content"),
                                        new HighlightField("author"),
                                        new HighlightField("category"))
                        ),
                        NewsSearchResult.class
                )).withSort(sortOptions)
                .build();
        // 2. 执行查询
        SearchHits<NewsEsDocument> searchHits = elasticsearchOperations.search(query, NewsEsDocument.class);

        // 3. 解析结果 & 提取高亮内容
        return searchHits.getSearchHits().stream().map(hit -> {
            NewsEsDocument doc = hit.getContent();

            // 获取高亮字段
            Map<String, List<String>> highlightFields = hit.getHighlightFields();

            String highlightedTitle = getHighlightedField(highlightFields, "title", doc.getTitle());
            String highlightedAuthor = getHighlightedField(highlightFields, "author", doc.getAuthor());
            String highlightedCategory = getHighlightedField(highlightFields, "category", doc.getCategory());
            String highlightedContent = getHighlightedField(highlightFields, "content", doc.getContent());

            // 对 content 高亮内容截取前 200 字符（避免过长）
            String finalContent = highlightedContent != null ? highlightedContent : doc.getContent();
            if (finalContent != null && finalContent.length() > 200) {
                highlightedContent = finalContent.substring(0, 200) + "...";
            } else {
                highlightedContent = finalContent;
            }

            return new NewsSearchResult(
                    doc.getId(),
                    highlightedTitle,
                    highlightedAuthor,
                    highlightedCategory,
                    highlightedContent,
                    doc.getPublishTime()
            );
        }).collect(Collectors.toList());
    }

    /**
     * 获取高亮字段，如果没有高亮则返回原始值
     */
    private String getHighlightedField(Map<String, List<String>> highlightFields, String fieldName, String defaultValue) {
        if (highlightFields.containsKey(fieldName) && !highlightFields.get(fieldName).isEmpty()) {
            // 取第一个高亮片段
            return highlightFields.get(fieldName).get(0);
        }
        return defaultValue;
    }


    /**
     * 构建 Elasticsearch 的 Document 对象
     *
     * @param news 数据库中的新闻实体
     * @return ES 映射实体
     */
    private NewsEsDocument buildEsDocument(News news) {
        NewsEsDocument doc = new NewsEsDocument();
        doc.setId(news.getId());
        doc.setTitle(news.getTitle());
        doc.setContent(news.getContent());
        doc.setAuthor(news.getAuthor());
        doc.setCategory(news.getCategory());
        doc.setPublishTime(news.getPublishDate());
        return doc;
    }
}
